#include "degfx/driver.h"

#ifdef MX__DRIVER_TURBOC

#   define MXMODULE_DRS

#   include <dos.h>
#   include <conio.h>
#   include <assert.h>
#   include <graphics.h>

/* Mouse variables */
static unsigned mx__turboc_mousevalid = false;

/* Scratch row for RGB888 to 4 bit color conversion */
static unsigned char *mx__turboc_scratch;

static unsigned mx__turboc_start(MX_GFX_ARGS * args)
{
	union REGS r;

	/* request auto detection */
	int gdriver = DETECT, gmode, errorcode;

	/* initialize graphics and local variables */
	initgraph(&gdriver, &gmode, "");

	/* read result of initialization */
	errorcode = graphresult();
	if (errorcode != grOk) {
		printf("Graphics error: %s\n", grapherrormsg(errorcode));
		printf("Press any key to halt:");
		getch();
		exit(1);				/* terminate with an error code */
	}

	/* Reset the mouse driver */
	r.x.ax = 0x0000;
	int86(0x33, &r, &r);
	if (r.x.ax)
		mx__turboc_mousevalid = true;
	else
		mx__turboc_mousevalid = false;

	/* Inform the user of the results */
	args->w = 640;
	args->h = 480;
	args->c = 4;
	args->title = "turboc_vga";
	args->pointer = false;

	mx_drs_area(args->w - 1, args->h - 1);

	return true;
}

static void mx__turboc_stop(void)
{
	closegraph();
	textmode(C80);
}

static void mx__turboc__flush(const MX_RECT * rect, MX_PIXEL * array, const int stride)
{
	int x, y;

	/* Push the buffer to the visible screen */
	y = rect->y1;
	while (y <= rect->y2) {

		/* Convert the line to 4 bit color in some temp space */
		const MX_PIXEL *src = array;
		unsigned char *ptr = mx__turboc_scratch;

		x = rect->x1;
		while (x <= rect->x2) {
			const unsigned int r = MXR(*src);
			const unsigned int g = MXG(*src);
			const unsigned int b = MXB(*src);

			*ptr = 0;
			if (r + b + g > 384)
				*ptr |= 8;
			if (-r + b + g > 127)
				*ptr |= 4;
			if (r - b + g > 127)
				*ptr |= 2;
			if (r + b - g > 127)
				*ptr |= 1;

			++src;
			++ptr;
			++x;
		}

		ptr = mx__turboc_scratch;

		x = rect->x1;
		while (x <= rect->x2) {
			putpixel(x, y, *ptr);
			++ptr;
			++x;
		}

		/* Next row to flush */
		array += stride;
		++y;
	}
}

static void mx__turboc_flush(MX_RECT * rect)
{
	mx__turboc_scratch = malloc(480);

	mx__gfx_flush(mx__turboc__flush, rect);

	free(mx__turboc_scratch);
}

static unsigned mx__turboc_poll(void)
{
	mx_drs_update(mx__turboc_flush);
	return true;
}

static void mx__turboc_dirty(const MX_RECT * rect)
{
	mx_drs_dirty(rect, true);
}

static unsigned mx__turboc_mouse(void)
{
	union REGS regs;

	if (!mx__turboc_mousevalid)
		return false;

	/* Get mouse button data */
	regs.x.ax = 0x0003;
	int86(0x33, &regs, &regs);
	mx__mouseb = regs.x.bx;
	mx__mousex = regs.x.cx;
	mx__mousey = regs.x.dx;

	return true;
}

static unsigned mx__turboc_key(int *scan, long *ascii)
{
	if (!kbhit())
		return false;

	*scan = 0;
	*ascii = getch();

	if (*ascii == 0)
		*scan = getch();

	return true;
}

const MX_DRIVER mx__turboc = {
	mx__turboc_start,
	mx__turboc_stop,

	mx__turboc_poll,
	mx__turboc_dirty,

	mx__turboc_mouse,
	mx__turboc_key
};

#endif
